package group.raber.saber.lang;

import group.raber.saber.kit.DateKit;
import group.raber.saber.kit.JSONKit;
import org.apache.commons.jxpath.AbstractFactory;
import org.apache.commons.jxpath.JXPathContext;
import org.apache.commons.jxpath.JXPathNotFoundException;
import org.apache.commons.jxpath.Pointer;
import org.apache.commons.lang3.Validate;

import java.text.DateFormat;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.regex.Pattern;

/**
 * Created by tisir<yangsong158@qq.com> on 2017-05-19
 */
public class DataBox extends LinkedHashMap<String,Object> implements Map<String,Object> {

    JXPathContext context = null;

    protected void toggleInit(){
        if(context==null){
            context = JXPathContext.newContext(this);
            //xpath路径中,自动创建对象使用的factory
            context.setFactory(getCreateObjectFactory());
        }
    }

    protected AbstractFactory getCreateObjectFactory(){
        return new AbstractFactory(){
            public boolean createObject(JXPathContext context, Pointer pointer,
                                        Object parent, String name, int index){
                if (parent instanceof DataBox){
                    DataBox dtx = (DataBox)parent;
                    dtx.put(name,new DataBox());
                    return true;
                }else{
                    return false;
                }
            }
        };
    }

    protected String xpath(String p){
        if(p==null)return null;
        return p.replaceAll("\\.","/");
    }

    /**
     * 获取一个值对象
     * @param expression xpath表达式
     * @param strict 是否使用严格模式
     * @return
     */
    public ValueObject xget(String expression,boolean strict){
        toggleInit();

        Object value = null;
        try{
            value = context.getValue(xpath(expression));
        }catch(JXPathNotFoundException e){  //如果表达式不存在,在严格模式下,也抛出错
            if(strict){
                throw new NullPointerException(e.getMessage());
            }
        }
        if(strict) Validate.notNull(value,"expression:"+expression,",not exits in object:"+toJsonString());
        return new ValueObject(value);
    }

    public ValueObject xget(String expression){
        return xget(expression,false);
    }

    public void xput(String expression,Object value){
        toggleInit();
        context.createPathAndSetValue(xpath(expression),value);
    }

    public String toJsonString(){
        return JSONKit.toJsonString(this);
    }


    public static class ValueObject{
        private Object value;
        private ValueType type;
        private static Pattern NUMBER_PATTERN = Pattern.compile("^(\\-|\\+)?\\d+(\\.\\d+)?$");
        private static DecimalFormat NUMBER_FORMAT = new DecimalFormat("#.#");

        public enum ValueType{
            String,Boolean,Number,Date,Object,Null
        }

        public ValueType getValueType() {
            return type;
        }

        public ValueObject(Object value){
            if(value==null){
                this.type = ValueType.Null;
            }else if(value instanceof String){
                this.type = ValueType.String;
            }else if(value instanceof Number){
                this.type = ValueType.Number;
            }else if(value instanceof Date){
                this.type = ValueType.Date;
            }else{
                this.type = ValueType.Object;
            }
            this.value = value;
        }

        public Object value(){
            return this.value;
        }
        public <T>T objectVal(Class<T> classType){
            return (T)value;
        }

        public Number numberValue(){
            if(type==ValueType.Number){
                return (Number)value;
            }else if(type==ValueType.String&&NUMBER_PATTERN.matcher((String)value).matches()){
                return Double.parseDouble((String)value);
            }else{
                throw new RuntimeException("value ["+value+"] is not a digital format");
            }
        }
        public Long longValue(){
            return numberValue().longValue();
        }
        public Integer intValue(){
            return numberValue().intValue();
        }
        public Double doubleValue(){
            return numberValue().doubleValue();
        }
        public String strValue(){
            if(type==ValueType.Number){
                return NUMBER_FORMAT.format(numberValue());
            }else if(type==ValueType.String){
                return (String)value;
            }else if(type==ValueType.Date){
                return (new SimpleDateFormat(DateKit.DATE_FORMAT)).format(dateValue());
            }else if(type==ValueType.Null){
                return null;
            }else{
                return value.toString();
            }
        }
        public Date dateValue(){
            if(type==ValueType.Number){
                return DateKit.parse(longValue());
            }else if(type==ValueType.String){
                return DateKit.parse(strValue());
            }else if(type==ValueType.Date){
                return (Date)value;
            }else if(type==ValueType.Null){
                return null;
            }else{
                throw new RuntimeException("value ["+value+"] is not a date format");
            }
        }
        public Boolean boolValue(){
            if(type==ValueType.String){
                return Boolean.parseBoolean(strValue());
            }else if(type==ValueType.Boolean){
                return (Boolean)value;
            }else if(type==ValueType.Null){
                return null;
            }else{
                throw new RuntimeException("value ["+value+"] is not a boolean format");
            }
        }

        /**
         * 值是否为NULL
         * @return
         */
        public boolean isNull(){
            return value == null;
        }

        /**
         * 值对象是否为没有内容,例如空字串,空集合等情况
         * @return
         */
        public boolean isEmpty(){
            if(isNull())return true;
            if(value.getClass().isArray()){
                return ((Object[])value).length==0;
            }else if(value instanceof Collection){
                return ((Collection)value).size()==0;
            }
            return false;
        }

        public String toString() {
            if(value!=null){
                return value.toString();
            }else{
                return "";
            }
        }
    }
}
